package com.example.traning.mall.api;

import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import com.example.traning.mall.controller.account.vo.CurrentPrincipal;
import com.example.traning.mall.dal.redis.account.AccountUserRedisDAO;
import com.example.traning.mall.dal.redis.account.UserCachePO;
import com.example.traning.mall.framework.common.ex.GlobalErrorCodeConstants;
import com.example.traning.mall.framework.common.util.json.JsonUtils;
import com.example.traning.mall.framework.common.util.servlet.ServletUtils;
import com.example.traning.mall.framework.security.core.service.OAuth2TokenApi;
import com.example.traning.mall.framework.security.core.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;

import static com.example.traning.mall.framework.common.consts.data.CommonConsts.ENABLE_STATE_ON;
import static com.example.traning.mall.framework.common.consts.web.JwtConsts.*;
import static com.example.traning.mall.framework.common.ex.ServiceExceptionUtil.exception0;

/**
 * @author lei yu
 * @since 2024/10/15
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2TokenApiImpl implements OAuth2TokenApi {

    private final AccountUserRedisDAO accountUserRedisDAO;

    @Override
    public void checkAccessToken(String accessToken) {

        JWT jwt = JwtUtils.parse(accessToken);
        // 从解析结果中获取用户的信息
        JWTPayload payload = jwt.getPayload();
        JSONObject claimsJson = payload.getClaimsJson();
        log.info("jwt claim => {}", claimsJson);

        Long userId = claimsJson.get(CLAIM_USER_ID, Long.class);
        String username = claimsJson.get(CLAIM_USER_NAME, String.class);
        String userAgent = claimsJson.get(CLAIM_USER_AGENT, String.class);
        String remoteAddr = claimsJson.get(CLAIM_REMOTE_ADDR, String.class);

        // 判断此次请求，与当初登录成功时的相关信息是否相同
        log.info("开始检查JWT是否存在盗用的问题……");
        if (!userAgent.equals(ServletUtils.getUserAgent()) && !remoteAddr.equals(ServletUtils.getClientIp())) {
            // 本次请求的信息与当初登录时完全不同，则视为无效的JWT
            log.warn("本次请求的信息与当初登录时完全不同，将直接放行，交由后续的组件进行处理");
            return ;
        }

        // 从缓存中读取用户登录信息
        log.info("开始检查缓存中用户的状态……");
        UserCachePO userState = accountUserRedisDAO.getUserState(userId);
        // 判断缓存中是否存在数据
        if (userState == null) {
            // 放行，不会向SecurityContext中存入认证信息，则相当于没有携带JWT
            log.warn("在缓存中无此JWT对应的信息，将直接放行，交由后续的组件进行处理");
            return ;
        }

        // 检查用户的启用状态
        Integer userEnable = userState.getEnable();
        if (!Objects.equals(ENABLE_STATE_ON,userEnable )) {
            accountUserRedisDAO.deleteUserState(userId);
            String message = "您的账号【" + username + "】已经被禁用，将强制下线！";
            log.warn(message);
            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), message);
        }

        // 续期缓存中的用户登录信息
        accountUserRedisDAO.renewal(userId);

        String authoritiesJsonString = userState.getAuthoritiesJsonString();
        log.info("从Redis中读取当前用户的权限列表 => {}", authoritiesJsonString);

        // 当事人
        CurrentPrincipal principal = new CurrentPrincipal();
        principal.setId(userId);
        principal.setUsername(username);

        // List<String>
        List<SimpleGrantedAuthority> permissions =
                JsonUtils.parseArray(authoritiesJsonString, SimpleGrantedAuthority.class);

        // 创建 Authentication，并设置到上下文
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                principal, null, permissions);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
